package owg.util.opengl.models;

import java.nio.ShortBuffer;
import java.nio.IntBuffer;

import com.jogamp.opengl.GL;
import com.jogamp.opengl.GL2;
import com.jogamp.opengl.GL3;
import com.jogamp.opengl.GLContext;

import owg.util.Calc;
import owg.util.opengl.EGL;
import owg.util.opengl.EGL3;
import owg.util.opengl.ShaderProgram;



/**
 * This object can be used to build and store VBOs in a conceptually simple and general way.
 * Is an implementation of High Performance Renderable using 16 bit shorts for vertex data.
 */
public class VBOS extends HPRS
{
	/**Update type*/
	int vUpdateMode, iUpdateMode;
	/**Pointer to the index of the interleaved VBO*/
	SharedObject vbo;
	ShortBuffer vBuf;
	/**Pointer to the index of the IBO*/
	SharedObject ibo;
	IntBuffer iBuf;
	
	/**Construct a new object which uses the vertex data from dataSrc
	 * Index data must be specified.*/
	public VBOS(VBOS dataSrc, int faceMode, String name)
	{
		super(name, faceMode, dataSrc.numVertexComponents, dataSrc.useNormal, dataSrc.numColorComponents, dataSrc.numTexCoordComponents, dataSrc.numTexCoordComponents2);
		this.vUpdateMode = dataSrc.vUpdateMode;
		this.iUpdateMode = dataSrc.iUpdateMode;
		
		vbo = dataSrc.vbo;
		vData = dataSrc.vData;
		vBuf = dataSrc.vBuf;

		//Is overwritten if vertices are added or setPrepackedVData is called.
		vBuildMode = BUILD_SHARED_BUFFER;
		
		ibo = dataSrc.ibo;
		iData = dataSrc.iData;
		iBuf = dataSrc.iBuf;

		//Is overwritten if indices are added or setPrepackedIData is called.
		iBuildMode = BUILD_SHARED_BUFFER;
	}
	/**Construct an empty object with the given settings*/
	public VBOS(String name, int vUpdateMode, int iUpdateMode, int faceMode, int nVertexComps, boolean useNormal, int nColorComps, 
		int nTexCoords, int nTexCoords2)
		{
		super(name, faceMode, (byte)nVertexComps, useNormal, (byte)nColorComps, (byte)nTexCoords, (byte)nTexCoords2);
		this.vUpdateMode = vUpdateMode;
		this.iUpdateMode = iUpdateMode;
		}
	/**Construct an empty object with the given settings*/
	public VBOS(int vUpdateMode, int iUpdateMode, int faceMode, boolean useNormal, boolean useColor, boolean useAlpha, 
		boolean useTexCoord,boolean use3DTexCoord, boolean useTexCoord2, boolean use3DTexCoord2, String name)
		{
		this(name, vUpdateMode, iUpdateMode, faceMode, 3, useNormal, (useColor?(useAlpha?4:3):0), (useTexCoord?(use3DTexCoord?3:2):0), (useTexCoord2?(use3DTexCoord2?3:2):0));
		}
	@Override
	public void requestVUpdate()
	{
		if(vbo != null)
			vbo.outdated = true;
	}
	@Override
	public void requestIUpdate()
	{
		if(ibo != null)
			ibo.outdated = true;
	}
	@Override
	protected void endImpl()
	{
		if(vBuildMode != BUILD_SHARED_BUFFER)
		{
			vBuf = ShortBuffer.wrap(vData);
			vbo = new SharedObject();
		}
		else
			vbo.increment();
		
		if(iBuildMode != BUILD_SHARED_BUFFER)
		{
			iBuf = IntBuffer.wrap(iData);
			ibo = new SharedObject();
		}
		else
			ibo.increment();
	}
	
	/* (non-Javadoc)
	 * @see graphics.HPR#render()
	 */
	@Override
	public void render() 
	{
		GL gl = egl.getGL();
		if(vbo.outdated)
		{
			if(vbo.obj == -1)
			{
				int[] bufferContainer = new int[1];
				gl.glGenBuffers(1, bufferContainer,0);
				vbo.obj = bufferContainer[0];
			}
			gl.glBindBuffer(GL.GL_ARRAY_BUFFER, vbo.obj);
			gl.glBufferData(GL.GL_ARRAY_BUFFER, vData.length*Calc.bytesPerShort, vBuf, vUpdateMode);
			vbo.outdated = false;
		}
		else
			gl.glBindBuffer(GL.GL_ARRAY_BUFFER, vbo.obj);
		if(ibo.outdated)
		{
			if(ibo.obj == -1)
			{
				int[] bufferContainer = new int[1];
				gl.glGenBuffers(1, bufferContainer,0);
				ibo.obj = bufferContainer[0];
			}
			gl.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, ibo.obj);
			gl.glBufferData(GL.GL_ELEMENT_ARRAY_BUFFER, iData.length * Calc.bytesPerInt, iBuf, iUpdateMode);
			ibo.outdated = false;
		}
		else
			gl.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, ibo.obj);
		
		if(gl.isGL3())
		{
			if(gl.isGL3()) 
			{
			EGL3 egl3 = ((EGL3) egl);
			GL3 gl3 = (GL3)gl;
			ShaderProgram program = egl3.getCurrentProgram();
			program.enableAllAttribs(vertexStride, numVertexComponents, vertexPointer, useNormal?3:0, normalPointer, 
				new int[]{numTexCoordComponents, numTexCoordComponents2}, new int[]{texCoordPointer, texCoordPointer2},
				numColorComponents, colorPointer);

			int end = 0;
			for(int i=0; i<faceModeEndIndices.size(); i++)
			{
				int currentFaceMode = faceModes.get(i);
				int start = end;
				end = faceModeEndIndices.get(i);
				if(renderLimit != -1)
				{
					if(start >= renderLimit)
						break;
					end = Math.min(end, renderLimit);
				}
				gl3.glDrawElements(currentFaceMode, end-start, GL2.GL_UNSIGNED_INT, start*Calc.bytesPerInt);
			}
			
			program.disableAttribs();
			}
		}
		else if(gl.isGL2())
		{
			GL2 gl2 = (GL2)gl;
			
			gl2.glVertexPointer(numVertexComponents, GL.GL_SHORT, vertexStride*Calc.bytesPerShort, vertexPointer*Calc.bytesPerShort);
			if(useNormal)
			{
				gl2.glNormalPointer( GL2.GL_SHORT, vertexStride*Calc.bytesPerShort, normalPointer*Calc.bytesPerShort);
				gl2.glEnableClientState(GL2.GL_NORMAL_ARRAY);
			}
			if(numColorComponents > 0)
			{
				gl2.glColorPointer(numColorComponents, GL.GL_UNSIGNED_SHORT, vertexStride*Calc.bytesPerShort, colorPointer*Calc.bytesPerShort);
				gl2.glEnableClientState(GL2.GL_COLOR_ARRAY);
			}
			if(numTexCoordComponents > 0)
			{
				gl2.glTexCoordPointer(numTexCoordComponents, GL.GL_SHORT, vertexStride*Calc.bytesPerShort, texCoordPointer*Calc.bytesPerShort);
				gl2.glEnableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
			}
			if(numTexCoordComponents2 > 0)
			{
				gl2.glClientActiveTexture(GL2.GL_TEXTURE1);
				gl2.glTexCoordPointer(numTexCoordComponents2, GL.GL_SHORT, vertexStride*Calc.bytesPerShort, texCoordPointer2*Calc.bytesPerShort);
				gl2.glEnableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
				gl2.glClientActiveTexture(GL2.GL_TEXTURE0);
			}
			gl2.glEnableClientState(GL2.GL_VERTEX_ARRAY);
	
			int end = 0;
			for(int i=0; i<faceModeEndIndices.size(); i++)
			{
				int currentFaceMode = faceModes.get(i);
				int start = end;
				end = faceModeEndIndices.get(i);
				if(renderLimit != -1)
				{
					if(start >= renderLimit)
						break;
					end = Math.min(end, renderLimit);
				}
				gl2.glDrawElements(currentFaceMode, end-start, GL2.GL_UNSIGNED_INT, start*Calc.bytesPerInt);
			}
			
			gl2.glDisableClientState(GL2.GL_VERTEX_ARRAY);
			if(useNormal)
				gl2.glDisableClientState(GL2.GL_NORMAL_ARRAY);
			if(numColorComponents > 0)
				gl2.glDisableClientState(GL2.GL_COLOR_ARRAY);
			if(numTexCoordComponents > 0)
				gl2.glDisableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
			if(numTexCoordComponents2 > 0)
			{
				gl2.glClientActiveTexture(GL2.GL_TEXTURE1);
				gl2.glDisableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
				gl2.glClientActiveTexture(GL2.GL_TEXTURE0);
			}
		}
		gl.glBindBuffer(GL.GL_ARRAY_BUFFER, 0);
		gl.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, 0);
	}
	

	@Override
	public synchronized void dispose(EGL<?> egl) 
	{
		if(vBuildMode != BUILD_COMPLETED || iBuildMode != BUILD_COMPLETED || vbo == null || ibo == null)
			System.err.println(this+" is in an invalid state: "+Calc.toString(vBuildMode, iBuildMode, vbo, ibo));
		dispose(egl, vbo, false);
		dispose(egl, ibo, false);
		//delete references (VBO synchronized on self)
		vbo = null;
		ibo = null;
	}
	@Override
	public synchronized void finalize()
	{
		if(vbo != null && vBuildMode == BUILD_COMPLETED)
			dispose(EGL.getEGL(GLContext.getCurrentGL()), vbo, true);
		if(ibo != null && iBuildMode == BUILD_COMPLETED)
			dispose(EGL.getEGL(GLContext.getCurrentGL()), ibo, true);
	}
	private void dispose(EGL<?> gl, SharedObject buf, boolean isFinalizeThread)
	{
		//Synchronize on the shared object so no conflicts can happen with other VBOF disposing/finalizing 
		synchronized (buf)
		{
			int n = buf.decrementAndGet();
			assert n >= 0 : "User count "+n+" can never be negative("+this+")";
			//If user count is 0 and the object has been initialised, delete it
			if(n == 0 && buf.obj != -1)
			{
				if(isFinalizeThread)
					egl.invokeLater(() -> {
						assert egl.getGL().glIsBuffer(buf.obj) : this+" was finalized with an invalid buffer "+buf.obj+"!";
						egl.getGL().glDeleteBuffers(1, new int[]{buf.obj}, 0);});
				else
					egl.getGL().glDeleteBuffers(1, new int[] { buf.obj }, 0);
			}
		}
	}
}
